Kwiz

The premise of our game, which we call Kwiz, is to answer movie-related questions based on movie posters (we use movie as a general term, which also includes series). After starting the game, players are shown a blurred image of a movie poster that increasingly unblurs over the course of a short time period.

As soon as players are ready to answer the question, they can tap the screen and are presented with a question related to the respective movie (e.g., “What is the name of the movie?”). The more seconds remain before tapping the screen, the more points users can collect with a correct answer. When guessing correctly, users can choose to double their points with a more challenging bonus question (e.g., “When was the movie released?”). Alternatively, users can collect their points and continue with the game until their lives have been depleted.

To incorporate exploration and learning, Kwiz presents users with a few key facts and trivia on a movie once the related questions have been completed. These facts include data from the main dataset (e.g., title and genre) as well as auxiliary data (e.g., movie posters).

Documentation

This wiki describes the project (Kwiz) that we (Alex Scheitlin, Roland Schläfli, and Nik Zaugg) have worked on as part of our course on Advanced Software Engineering (Project Instructions) at the University of Zurich.

Repositories

We use separate repositories for the different microservices and combine them, alongside several auxiliary repositories, in an umbrella repository that is used for documentation, project planning, and files that need to be shared across all services (e.g., docker-compose.yml).

The structure of the umbrella repository can be broken down as follows:

Repository Description Quality
ionic-app contains the sources for the application frontend
nest-api contains the sources for the GraphQL backend
metadata-service contains the sources for the service managing movie metadata
poster-service contains the sources for the service managing OMDB API requests
kwiz-dotfiles contains shared rulesets that make sure formatting and linting are consistent
kwiz-utils contains library code that is used in more than one service (DRY)
kwizapp.github.io contains the sources for https://kwizapp.github.io, our presentation deployment
wiki contains the documentation sources (the ones you are reading) in a .md format

What you can find here

The following provides a short overview of our documentation structure:

Topic Description
Project Overview (current chapter) Introduces our project and the datasets we have used to develop it, as well as the issues we still have on our roadmap.
Architecture Describes the architecture of our microservice application and how we integrate everything into a single service.
Development Describes how the project was planned, how we organized ourselves and explains how we applied a agile software development methodology.
Continuous Integration Introduces and motivates the devops practices (as taught in the course) that we have applied to our project by means of automated systems, tooling, and workflows.
Reference Provides an overview of all the APIs that we have developed for the purpose of our application.

Data Sources

Datasets and APIs provide the backbone for the functionality of our application. Kwiz depends on two main sources of information:

  • The Movies Dataset
  • The OpenMovie Database (OMDb)

Each of the data sources used in Kwiz is managed by a separate microservice. More specifically, the metadata-service returns metadata for a given movie whereas the poster-service returns a movie poster for a given IMDb ID.

More information on the structure of our microservices can be found in the Architecture section.

The Movies Dataset

as published on Kaggle

This dataset contains several .csv files with metadata including 26 million ratings from 270,000 users for 45,000 movies. Ratings are on a scale of 1-5 and have been obtained from the official GroupLens website.

The Open Movie Database (OMDb)

as exposed through an API

This API currently provides over 280,000 movie posters. It is a RESTful web service that can be accessed once an API Key has been generated.

Discarded Dataset: movielens-20m-posters-for-machine-learning

as published on Kaggle

Initially, the plan was to use this dataset to self-distribute movie posters. After realizing that this dataset yields very low fidelity links to posters and does not provide a mapping of imdb-ids to posters, we decided to drop this dataset and opted for the above API instead.

Roadmap-Changelog

Project Timeline

main features and milestones

The main features of each milestones are also listed below.

MVP

completed: April 27th 2020

Project Management

  • Setup a GitHub Organization, Repositories, and Wiki
  • Create User Stories and Issues and Integrate on Kanban

Devops

  • Setup Github Actions for Docker Build
  • Setup Github Actions for Deployment to Heroku

Code Quality

  • Integrate SonarCloud with Repositories
  • Create NPM Packages for Shared Code Style
  • Create Issue and Pull Request Templates
  • Setup Mandatory Code Review and Status Checks (before Merge)

Data Hydration

  • Filter unwanted or unfitting movies from “The Movies Dataset”
  • Segment movies by release year (0-1960, 1960-1990, 1990-2020)
  • Seed the database with an excerpt of the resulting list of movies

Metadata Microservice

  • Return a list of N random movies
  • Filter the above list according to initial criteria (different from ImdbID X, release year X)

Poster Microservice

  • For a given ImdbID, return the poster path as queried from OMDB
  • Specify size of poster with size url parameter

Backend

  • Design and Implement GraphQL schema
  • Setup connection to the Metadata Service
  • Setup connection to the Poster Service
  • Provide a random movie with metadata and poster url
  • Provide multiple random movies to generate a question
  • Compute the score of a submitted solution

Frontend

  • Welcome Screen
  • Basic Routing
  • Movie Guessing
  • Trivia Screen
  • Scoring System

V1.0.0

completed: May 22nd 2020

Organization

  • Setup GitHub Pages website

Frontend

  • Bonus Question Screen
  • Consistent Design
  • Track streak: how many movies guessed correctly in a row

Backend

  • Bonus Question scoring

Data

  • seed additional movies

V2.0.0

While we have finished an initial v1.0.0 of Kwiz, there are many ideas left to be implemented. We have added ideas for future work to a v2.0.0 milestone and dedicate this space to shortly introduce the most prominent ones.

Related Movies

Computing questions based on a set of related movies, rather than showing the movie in question alongside three randomly chosen movies, would provide the foundation for many other features (some of which are also described here).

Additional Question Types

Optionally, in the future, the current state of the game can be extended with additional question types by combining the existing dataset with more external datasets and API resources.

Such question types could allow for guessing...

  • … the overall rating of a movie.
  • … if a particular actor played in a movie.
  • … how much the production of a movie cost.

Multiplayer Mode

  • … a “multiplayer mode” with online scoreboards and playing against friends

Game Parameters

  • … varying levels of difficulty, either by choice or automatic increase
  • ... choice of movies to be played with (e.g., based on genres or time-frames)

"Movie Ninja"

  • … a more gamified “Fruit Ninja” mode where wrong facts need to be slashed

Design

We decided on the following guidelines when working on design:

  • The UI should be easy to understand and use
  • The design should be colorful but follow a consistent theme
  • The UI should be clean and simple

Mockups

Based on our guidelines, we set out to develop mockups in Figma:

All of our mockups can be viewed directly on Figma.

Current Design

While implementing the application, we continuously improved and evolved our design, yielding a much more consistent layout than imagined in the mockups:

Risk Analysis

Initial Assessment

The overall context of our application architecture, namely developing everything using web technologies, has been chosen such that all project members have some prior experience that they can apply to the project. Everyone on the team possesses significant knowledge on React development, and we all know how to work with TypeScript and popular Node.js frameworks. We also possess know-how on working with GraphQL, even though not everyone has worked with it before.

Out of personal interest to learn something new, we introduce two new frameworks into our application stack: Ionic and Nest.js. The introduction of Ionic allows us to develop mobile applications while applying our foundational knowledge of web technologies and React, while using Nest.js allows us to learn more about how a NodeJS API can be structured in a maintainable way.

Using these technologies incurs a level of risk in that we cannot definitively assess how much time will be spent on learning them and what features can or cannot be implemented using them. However, preliminary evaluation has shown that Ionic is very similar to normal React applications (besides its different build process)., and that Nest.js is structurally similar to well-known MVC frameworks like Java Spring. Therefore, our assessment is that it is well worth learning these new technologies to broaden our knowledge, even though there is a risk of additional time investment.

Team Kwiz, March 2020

Given that we chose to use a few new technologies, our initial assessment (as seen in the quote above) was quite optimistic. Now that we are about to conclude our project, we can say that we have mostly made good choices regarding technologies and processes. We would probably replace our frontend framework with React Native if we were to start over, as the Ionic framework, even though it is just web technologies, caused us a lot of frustration. While none of us has much experience with React Native, using it would probably result in a much more interesting and enjoyable development experience (but not necessarily in a "better" product).

For the remainder of our initial technical decisions, we have found that we would still approach them in the same way: our backend has been quite interesting to develop and the MVC approach gave the code a structure that is often missing in Node.js projects. Furthermore, the microservices have been very easy to develop and have caused almost no overhead due to the simplicity of the micro framework. We were able to get the microservices working very early on, which we attribute at least partly to our choice of technologies.

What we did not explicitly state in our initial assessment were the many procedural decisions that we had to make while working on the project (as described in depth in the remainder of this wiki). When looking at our working process and the continuous progress we were able to make, we identified a few key points that stand out:

  • Creating mockups on whiteboards and using tools allowed us to very quickly generate ideas and prototype without being held up by technicalities or needing to learn new technologies first
  • Creating user stories allowed us to imagine the potential use cases for Kwiz and enabled us to know when something was "done", as well as ensuring that we were working on a common "vision" of our project (in conjunction with mockups)
  • Implementing continuous integration with linting, formatting, and testing early on greatly reduced the manual workload, as well as the potential for discussions, over the long run

Architecture

Kwiz is a cross-platform application built using the Ionic framework and React. This allows for easy deployment to both web and mobile platforms without having to specifically optimize code for each deployment.

The backend of the application is based on Nest.js and communicates with the frontend using GraphQL. Both Ionic and Nest.js are based on Node.js and TypeScript, which enables a seamless switch between projects while sharing much of the auxiliary code for development and deployment.

The data backing the application is be provided by two microservices based on the minimal micro framework for Node.js. Each of these microservices provides different data related to movies. The metadata-service provides key information about movies and is connected to a postgres database. The poster-service on the other hand provides movie posters for specific IMDb IDs. This service has no database, but talks directly to another API (OMDB API). Both services expose a REST-API for data fetching. The backend will fetch data from these microservices independently and provide the game with all data through the unified GraphQL endpoint.

Applying a microservice architecture allows us to profit in several areas:

  • Modularity: Each service has one job and is isolated from the others, making development and testing easier
  • Scalablity: Each service runs, and can be scaled, independent of the others
  • Development: As the services are independent, the team can work in parallel and with distributed responsibilities
Topic Description
Ionic App Describes the frontend of Kwiz, the Ionic App.
Nest API Describes the main API of Kwiz, the Nest API.
Metadata Service Describes one of the data sources for Kwiz.
Poster Service Describes one of the data sources for Kwiz.

Ionic App

The Ionic App is responsible for providing the user with the interface to our application. The Frontend is built using modern web technologies such as ReactJS and Typescript. Furthermore, we leverage the Ionic Framework, which is an

open source UI toolkit for building performant, high-quality mobile and desktop apps using web technologies. Ionic is the only mobile app stack that enables web developers to build apps for all major app stores and the mobile web from a single codebase.

The main idea behind the Ionic Framework is providing a reliable way to distribute software onto all major platforms by only writing code once. The Ionic Framework also provides a set of UI React Components that are specifically themed for the platform they are deployed to. This allows for fast prototyping and great developer experience, while still adhering to the design guidelines of the supported platforms (e.g., Android and iOS).

The Kwiz Ionic App is distributed as a Single Page Application (SPA). While bottlenecks could occur when many clients request the same (static) resources, we could then think about distributing the application onto various CDN nodes that are closer to the client.

Nest API

The Nest API is responsible for fetching data from the Metadata Service and the Poster Service and is used by the Ionic App as the single source of data through the exposed GraphQL API. As the name suggests, the API uses Nest.js, which is

a progressive Node.js framework for building efficient, reliable and scalable server-side applications

The Nest application itself is stateless, meaning that incoming requests are answered by querying the two services for metadata and posters, structuring the data, and returning it whereby no information is stored persistently. Based on that and with the use of Docker, it is easy to scale the application.

While there are two REST services providing all the required data used for Kwiz, introducing this GraphQL API is advantageous because of the following reasons:

  • There is only one endpoint the frontend (Ionic App) needs to query to get the relevant data.
  • The whole application logic is separated from both the user interface and data sources.
  • The interface and resolvers of GraphQL provide a convenient way of querying the data and selecting different fields from objects from different pages of the React application.
  • Substituting or extending the data sources is easy due to the modularized style of the architecture.

Metadata Service

The Metadata Service provides an API for movie metadata, which includes properties like a movie's title, revenue, release date, and so on. The service is built using micro, a library that was specifically developed for asynchronous HTTP microservices. It is highly performant, simple to deploy, and very lightweight.

Micro — Asynchronous HTTP microservices

The data the API provides is stored in a Postgres Database, which we are currently hosting on Heroku. The Metadata Service itself is stateless, meaning that it does not depend on any particular state beside the database and can, therefore, be scaled easily. To hydrate the database, we wrote custom Python scripts that handle data cleaning, preprocessing, and hydrating the database.

Poster Service

The Poster Service provides an API that serves as an abstraction to the OMDB API. It is based on micro, a library specifically developed for asynchronous HTTP microservices. It is highly performant, simple to deploy, and very lightweight.

Micro — Asynchronous HTTP microservices

The Poster Service is stateless, meaning that it does not store any particular state and can, therefore, be scaled easily should it be necessary. The service only gets an IMDb-ID as a param and queries the OMDB API, after which it returns a high-fidelity poster path. We do not need any data from the OMDB API besides the poster path.

Development

Topic Description
Project Planning Describes the usage of user stories, issues, pull requests and the kanban board.
Version Control Explains the workflow with git using branches, tags, squashing, and reviewing.
Dependency Management Presents the tool we use to ensure the application uses the latest dependencies.

Project Planning

High Level

  • we use a GitHub Kanban Board board to track stories/tasks/bugs as GitHub issues. Every implementation task and user story is formulated as a Github Issue that can be referred to from a PR when making changes anywhere in the code base.
  • issues were worked on in a separate branches
  • every merge needed a Pull Request and at least 1 review (2 from master into dev)

Kanban Board

Development Workflow

Once the development started, the following methodology was applied:

Milestones

For this project we decided against using pre-defined sprints but to work in larger time spans - Milestones:

MVP - minimum viable product

v1.0.0 - stable version with all planned features for version 1.0.0

v2.0.0 - additional features

detailed descriptions of the features in each milestone can be found here

Project Planning

At the beginning of the project the following steps were taken to plan the work to be done:

  • brainstorm ideas about possible features for the application
  • create mockups screens in Figma
  • decide which technologies we want to use

Coordination and Work Distribution

Work distributions was generllay considered: "first come first served" - meaning that whenever you did not have any issue assigned to yourself, you could pick one from the board. Also, we did not split up our team into backend/frontend responsibilities. This worked well for everyone, as we already knew each other before the course and we were sure that everyone would contribute their share to the project.

Weekly Meetings: We held online meetings in Microsoft Teams each week to decide on which features to implement next and to talk about the current progress, eventual problems and challenges. These meetings were very valuable, as it helped us stay on track for the goals that we set for ouselves (Milestones).

User Stories

In a next step, the GitHub Project was initialized with a Github Kanban Board. This step also involved having a meeting and writing User Stories, Issues and decide on the priority of each feature. In the end, we added the following user stories to the User Stories column.

  • Story: Metadata API - provide movie metadata
  • Story: Poster API - provide movie poster urls
  • Story: Landing Screen - the home screen that players will see when they launch the application
  • Story: Blurred Poster Screen - display a blurred movie poster which gets unblured over time
  • Story: Question Screen - screen and mechanic for displaying possible answers to the user
  • Story: Success Screen - screen and mechanic for when the player aswers a question correclty
  • Story: Failure Screen - screen and mechanic for when the player aswers a question wrong
  • Story: Trivia Screen - display interesting and related trivia to the currently asked for movie
  • Story: Game Over - screen and mechanic for when the player looses
  • Story: Points Overlay - components and mechanics to display and track the players points and lives
  • Story: Question Screen (Bonus) - additional question type where the player can double the points
  • Story: Game Parameters - allow for playing the game with different settings (e.g., difficulty)

Each user story has the same structure and includes multiple acceptance criteria. We enforced the structure by creating a Github Issue Template.

Issues and Tasks

Before implementing any feature, issues regarding the feature needed to be created. For this, the user stories were split up into more fine-grained tasks which we tracked as Issues on our board. The issues also followed a predefined structure as specified in our organization wide Github Issue Template. Whenever there was a story that belonged to the issue, we added a reference to the original story in the issue description.

In a final step, the issue was labeled and added to the project board. From there, any of the team members could assign the issue to himself and start working on it.

Example Workflow

The following screenshots show an outtake of a feature implementation, namely The Question Screen, which shows 4 possible options that the user can choose from (select which movie was shown before).

1. The issue is created and describes the criteria that need to be fulfilled

2. After implementing the feature, the Pull Request is created

3. Every merge into dev needs at least one approval. The PR is reviewed.

4. After making the requested changes, the PR can be merged. Multiple CI checks ensure good code quality.

Version Control

We use Git to collaboratively work on the same code base and manage different releases of our code. GitHub is, among other things, used as a remote repository and to create an organization and structure for the different repositories of our application.

Simplified Git-Flow

To manage changes to our source code, we use a simplified Gitflow Workflow.

More specifically, we apply the following procedures:

  • The master branch serves as the release branch and is automatically deployed to production using Github Actions. Releases are tagged on the master branch to provide easy access to release snapshots.
  • The dev branch contains the latest changes to the source code that have been merged but not yet released. The dev branch is automatically deployed to a separate staging environment.
  • Separate feature branches are created for each code change.
  • Direct (force) pushes to the dev or master branch have been disabled.

Pull Requests and Code Review

Merging changes from feature branches to the dev branch or from the dev branch to the master branch involves making a pull request and going through a manual/automated review/approval cycle. Whenever possible, restrictions are enforced using settings in the Github interface.

Merging Feature Branches (feature -> dev)

To merge a code change into the dev branch, the following restrictions apply for merges across the services:

  • PRs must be approved by at least one reviewer (other than the author)
  • The following automated status checks must pass:
    • Formatting and linting (check) are coherent with our guidelines
    • Building the docker container (build_docker_hub) yields a valid image

When a feature branch is merged into the dev branch, it must be squashed to ensure a clean history on the dev and master branches.

Cutting a New Release (dev -> master)

In addition to all of the restrictions that apply for feature branches, the following additions apply:

  • PRs must be approved by at least two reviewers (other than the author)
  • Code owners, if defined, must approve any changes to files that they own

Once a release has been merged into the master branch by means of a release PR (e.g., Release v1.0.1), a corresponding tag is created to make snapshots easily accessible.

Auxiliary Repositories

While the crucial auxiliary repositories (kwiz-utils and kwiz-dotfiles) are subject to the same restrictions as listed above, less crucial ones apply relaxed procedures where PRs can be made directly against the master branch, and direct pushes can be permitted.

Dependency Management

To ensure our project dependencies stay up-to-date, we use https://dependabot.com/ across all of our services. With this setup, it should, in principle, not be necessary to update dependencies manually (even though that might sometimes be more efficient).

Dependabot will perform a weekly check of all project dependencies and open a Pull Request for any dependencies that are outdated. As these pull requests include changelogs as well as links to all of the relevant resources, this procedure makes it very convenient to keep a project up-to-date.

While it would also be possible to have Dependabot automatically merge dependencies based on CI status and predefined rules, we have decided to manually approve any changes to our application dependencies.

One example of a Dependabot PR can be found here: https://github.com/kwizapp/ionic-app/pull/141

Continuous Integration

We use GitHub Actions to automate our workflow and build a continuous integration (CI) pipeline.

After adding changes to the source code and checking them into Git, the commits are pushed to GitHub. As soon as a pull request is opened the GitHub Action is triggered and the whole CI pipeline starts (only on PRs merging to the dev or master branch). First, quality checks are run, such as linting (ESLint) and formatting (Prettier) and using Sonarcloud to run different checks on the code (Sonarcloud). Then, all tests are run and the docker image gets built and pushed to Docker Hub. Once the PR is merged, the application is released on the appropriate Heroku deployment. Every step is only started if the previous one has succeeded.

The following table gives a brief overview of the steps involved in the CI pipeline. Read the respective sections to get detailed information about each step.

Topic Description
Code Quality Shows how we ensure a high level of code quality using linters, static analysis and more.
Testing Shows how we use unit and end-to-end tests for new features.
Build Describes how we package our application with Docker.
Deployment Explains the different types of deployments we use.

Have a look at one of the GitHub Actions to see how the CI pipeline is configured (e.g., ionic-app).

Code Quality

To ensure a high level of source code quality, we use a bunch of different tools and techniques which are described below. As the source code of all repositories is written with TypeScript and JavaScript, most of the tools and techniques can be applied in all repositories.

Source Code Documentation

We use different types of documentation for kwiz which are listed below. This ensures that people new to this repository can understand (i) what this application is about, (ii) how it was implemented, (iii) how it can be extended, and (iii) how it can be built and deployed.

  • READMEs (e.g., metadata-service) that describe the purpose and structure of the repository, how it can be installed, started, and tested.
  • API Documentations (e.g., nest-api) describe in detail which requests can be made to the REST and GraphQL endpoints and what the parameters and their default values are as well as examples of requests and their responses.
  • The Component Dcoumentation (ionic-app only) describes the React pages and UI components of the Ionic application and which parameters they have.
  • This Wiki that explains the idea of the kwiz application and provides all related documentation.
  • Source Code Comments using JSDoc to explain what the purpose of a class or module is and how a single method or line of code works. An example is provided below:
/**
 * Build the URL to get information about a specific or one/multiple random
 * movie(s) from the `MetadataService`.
 *
 * @param imdbId - The IMDb ID of the movie to fetch some metadata for.
 *                 This parameter is optional. If it is not supplied, one or
 *                 more random movies are returned.
 * @param numMovies - The number of random movies to return.
 *                    This parameter is optional and only considered if there
 *                    is no IMDb ID given. If this parameter is not supplied,
 *                    exactly one movie will be returned.
 * @param differentFrom - An optional IMDb ID specifying a movie that should not be
 *                        included in the returned movies. If it is not supplied,
 *                        any movies can be returned.
 * @param differentReleaseYear - An optional parameter specifying a year, in which
 *                               non of the returned movies should be released in.
 */
export const buildMetadataServiceURL = (
  imdbId: string = null,
  numMovies: number = 1,
  differentFrom: string = null,
  differentReleaseYear: number = 0,
): string => {
  ...
};

Linting and Formatting

We apply appropriate ESLint rules across our projects to statically analyze the source code to find and fix problems. Moreover, we use Prettier to format the source code to ensure consistency across commits of different developers.

Both checks are automatically performed before every commit (locally) and also integrated in the CI pipeline.

To ensure consistency of linting and formatting rules across repositories, we have created an auxiliary repository with packages that contain configurations that we can reuse across the entire organization. See the packages for more information.

Static Analysis

To ensure good code quality, we use the Sonarcloud platform to run static analysis on our code.

Service Maintainability
ionic-app
nest-api
metadata-service
poster-service

Issue and Pull Request Templates

To work with uniform issue and pull request descriptions and to guarantee that every step of our workflow has been followed before new changes are introduced into the code base, we use custom Templates providing the structure and check lists for issues and pull requests. Amongst others, they require that tests haven been written/adjusted and that the documentation has been extended. Read more about testing here.

Testing

Our development process requires that only tested source code gets merged into the development branch (and later into the master branch). Thus, the pull request template on GitHub contains an item mentioning to write tests if necessary:

Have you added tests where necessary? Do all the test pass?

To ensure that the implemented source code works as expected, we use both unit and end-to-end (e2e) tests. We run the tests locally, after each push to a pull request with GitHub Action and before building and deploying the docker images and the application.

We use the testing framework Jest for both unit and e2e tests and supertest for the e2e tests to facilitate HTTP testing. To test the services of the nest-api we use Jest's mocking utilities.

Below are two examples of e2e tests testing the (i) REST API of the metadata-service and (ii) GraphQL API of the nest-api:

it('fetches a list of random movies filtered to be different from a given movie',
    async () => {
  const response = await request(micro(server)).get('/?numMovies=3&
    differentFrom=tt0076759')
  expect(response.statusCode).toEqual(200)
  expect(response.body.length).toEqual(3)
  response.body.forEach((movie) =>
    expect(movie).toMatchObject({
      date_segment: expect.any(String),
      imdb_id: expect.any(String),
      title: expect.any(String),
    }),
  )
  expect(response.body.map((movie) => movie.imdb_id)).not.toContain('tt0076759')
})
it('should return a specific movie with two random movies not released in the same
    year as the specified movie', () => {
  return request(app.getHttpServer())
    .post('/graphql')
    .send({
      query: `{
        movie(imdbId: "tt2395427") {
          imdbId title releaseYear posterPath
          randomMovies(num: 2, differentReleaseYear: true) {
            imdbId title releaseYear posterPath
          }
        }
      }`,
    })
    .expect(200)
    .then((response) => {
      const movie = response.body.data.movie
      expect(movie.imdbId).toBe(MOVIE.imdbId)
      expect(movie.title).toBe(MOVIE.title)
      expect(movie.releaseYear).toBe(MOVIE.releaseYear)
      expect(movie.posterPath).toBe(MOVIE.posterPath)

      expect(movie.randomMovies.length).toBe(2)

      let randomMovie = movie.randomMovies[0]
      expect(randomMovie.imdbId).not.toBe(MOVIE.imdbId)
      expect(randomMovie.title).not.toBe(MOVIE.title)
      expect(randomMovie.releaseYear).not.toBe(MOVIE.releaseYear)
      expect(randomMovie.posterPath).not.toBe(MOVIE.posterPath)

      randomMovie = movie.randomMovies[1]
      expect(randomMovie.imdbId).not.toBe(MOVIE.imdbId)
      expect(randomMovie.title).not.toBe(MOVIE.title)
      expect(randomMovie.releaseYear).not.toBe(MOVIE.releaseYear)
      expect(randomMovie.posterPath).not.toBe(MOVIE.posterPath)
    })
})

Build

After the CI Pipeline passed the linting and formatting checks and all tests have been executed successfully, the docker docker images are built and pushed to docker hub.

Deployment

All micro-services are automatically deployed to Heroku as a last step of the continous integration pipeline. A new deployment will be triggered every time a pull request to one of the two main branches is made (either to dev or master).

Deployment Steps

Each deployment consists of the following steps:

  • Pulling the latest docker image from Docker Hub
  • Build the docker image
  • Push the docker image to Heroku
  • Release the new version on Heroku

Deployment Types

Further there are two different types of deployments:

Staging Deployments

PRs to the dev branch trigger a staging deployment to the following heroku applications:

Service URL
metadata-service https://kwiz-metadata-service-stage.herokuapp.com
poster-service https://kwiz-poster-service-stage.herokuapp.com
nest-api https://kwiz-nest-api-stage.herokuapp.com/graphql
ionic-app https://kwiz-ionic-app-stage.herokuapp.com

Production Deployments

PRs to the master branch trigger a staging deployment to the following heroku applications:

Service URL
metadata-service https://kwiz-metadata-service.herokuapp.com
poster-service https://kwiz-poster-service.herokuapp.com
nest-api https://kwiz-nest-api.herokuapp.com/graphql
ionic-app https://kwiz-ionic-app.herokuapp.com

The production application is also available on https://kwizapp.github.io to demo the mobile app.

_Note that heroku dynos go to sleep after one hour of inactivity and need some time to wake up after the first request._

Reference

Reference Description
Components: Ionic App Description of different pages and UI components
API Reference: Nest API GraphQL API endpoints providing movie data and scoring functionality
API Reference: Metadata Service REST endpoints providing information about movies
API Reference: Poster Service REST endpoints providing URLs to movie posters

Components: Ionic App

Go to Repository

The ionic-app is the frontend of the kwizapp and is built with ReactJS on top of the Ionic Framework.

General Structure

The source code is divided into Pages and Components, this was a design choice made by the team as it helps to separate top-level components such as Home.tsx from more granular and reusable components such as the KwizButton.tsx.

This figure shows the top-level components and the user flow through the application.

Documentation

We create documentation for all the react component right from the source code. For this, we use styleguidist to generate a static documentation page. E.g., for the AnswerCard.tsx component:

The documentation is published here: https://kwizapp.github.io/ionic-app/

API: Nest API

Go to Repository

The nest-api provides the following requests to:

  • Fetch metadata and the poster of a random movie
  • Fetch metadata and the poster of a specific movie
  • Optionally, get n random movies different from the random/specific movie
  • Score a "poster guessing" question
  • Score a "bonus" question

All requests should be made to the /graphql endpoint (e.g., http://localhost:3000/graphql).

Fetching a specific or random movie

Parameter Type Default Description
imdbId string - Optional. IMDb ID, uniquely identifies a movie.
randomMovies field - Optional. Specifies that one or multiple additional random movie(s) should be returned.
num int 1 Optional. Specifies how many additional random movies should be returned.
differentReleaseYear boolean false Optional. Specifies whether the random movie(s) should have a different release year than the specified one.

Note:

  • If imdbId is used, a specific movie is returned. If the parameter is not added, a random movie is returned.
  • If randomMovies is added but no num is specified, one additional random movie is returned. randomMovies can completely be omitted, so that no additional random moves are returned.
  • If differentReleaseYear is added but no imdbId is specified, differentReleaseYear will not be considered.

Example

Request:

query {
  movie(imdbId: "tt1431045") {
    imdbId title releaseYear posterPath
    randomMovies(num: 3, differentReleaseYear: true) { imdbId title releaseYear posterPath }
  }
}

Response:

{
  "data": {
    "movie": {
      "imdbId": "tt1431045",
      "title": "Deadpool",
      "releaseYear": 2016,
      "posterPath": "https://m.media-amazon.com/images/M/MV5BYzE5MjY1ZDgtMTkyNC00MTMyLThhMjAtZGI5OTE1NzFlZGJjXkEyXkFqcGdeQXVyNjU0OTQ0OTY@._V1_SX300.jpg",
      "randomMovies": [
        {
          "imdbId": "tt1318514",
          "title": "Rise of the Planet of the Apes",
          "releaseYear": 2011,
          "posterPath": "https://m.media-amazon.com/images/M/MV5BYzE3ZmNlZTctMDdmNy00MjMzLWFmZmYtN2M5N2YyYTQ1ZDJjXkEyXkFqcGdeQXVyNTAyODkwOQ@@._V1_SX300.jpg"
        },
        {
          "imdbId": "tt0076759",
          "title": "Star Wars",
          "releaseYear": 1977,
          "posterPath": "https://m.media-amazon.com/images/M/MV5BNzVlY2MwMjktM2E4OS00Y2Y3LWE3ZjctYzhkZGM3YzA1ZWM2XkEyXkFqcGdeQXVyNzkwMjQ5NzM@._V1_SX300.jpg"
        },
        {
          "imdbId": "tt3896198",
          "title": "Guardians of the Galaxy Vol. 2",
          "releaseYear": 2017,
          "posterPath": "https://m.media-amazon.com/images/M/MV5BNjM0NTc0NzItM2FlYS00YzEwLWE0YmUtNTA2ZWIzODc2OTgxXkEyXkFqcGdeQXVyNTgwNzIyNzg@._V1_SX300.jpg"
        }
      ]
    }
  }
}

Get scores for a "poster guessing" question

Parameter Type Description
imdbId string IMDb ID, uniquely identifies a movie. This is the id of the blurred poster that was shown.
selectedTitle string The title that was selected as the answer.
remainingSeconds int The number of remaining seconds when the player tapped the screen to guess the movie title.

Example

Request:

query {
  scoreTitleResponse(
    imdbId: "tt1431045",
    selectedTitle: "Deadpool",
    remainingSeconds: 5
  )
}

Response:

If the player scores zero points, the answer is incorrect and therefore one life is deducted.

{
  "data": {
    "scoreTitleResponse": 500
  }
}

Get scores for a "bonus" question

Parameter Type Description
imdbIds [string] IMDb IDs, uniquely identifying movies. Ordered by release year in increasing order.*
titleQuestionScores int The points scored in the "poster guessing" question.

* This means that the player choses the order of the movie releases. The imdbIds are then sent to the API in this order. The API then checks whether they are sorted by their release years in increasing order.

Example

Request:

query {
  scoreBonusResponse(
    imdbIds: ["tt0145487", "tt0468569", "tt1431045"],
    titleQuestionScores: 350
  )
}

Response:

If the answer is correct, the points are doubled. If the answer is incorrect, no points are awarded.

{
  "data": {
    "scoreBonusResponse": 700
  }
}

API: Metadata Service

Go to Repository

This service provides metadata for movies. The metadata-service API is simple and provides two main modes of operation:

  1. Fetching a single, specific movie given its ImdbID
  2. Fetching a list of random movies (1-n items) with optional filtering applied

Fetching a specific movie

/?imdbId=<id>

Parameter Type Default Description
imdbId ImdbID undefined Optional. IMDb ID, uniquely identifies a movie. The service will return random movie(s) if the parameter is not given.

Example:

http://localhost:3003/?imdbId=tt3450958

Returns:

[
  {
    "imdb_id": "tt3450958",
    "budget": "152000000",
    "homepage": "http://www.foxmovies.com/movies/war-for-the-planet-of-the-apes",
    "original_language": "en",
    "original_title": "War for the Planet of the Apes",
    "overview": "Caesar and his apes are forced into a deadly conflict with an army of humans led by a ruthless Colonel. After the apes suffer unimaginable losses, Caesar wrestles with his darker instincts and begins his own mythic quest to avenge his kind. As the journey finally brings them face to face, Caesar and the Colonel are pitted against each other in an epic battle that will determine the fate of both their species and the future of the planet.",
    "popularity": 146.161786,
    "poster_path": "/3vYhLLxrTtZLysXtIWktmd57Snv.jpg",
    "release_date": "2017-07-11",
    "revenue": 369907963,
    "runtime": 140,
    "status": "Released",
    "tagline": "For freedom. For family. For the planet.",
    "title": "War for the Planet of the Apes",
    "video": false,
    "vote_average": 6.7,
    "vote_count": 1675,
    "release_year": 2017,
    "date_segment": "2"
  }
]

Fetching a list of random movies

/?numMovies=<MovieCount>&differentFrom=<OtherImdbID>&notReleasedIn=<ReleaseYear>

Parameter Type Default Description
numMovies Integer 1 Optional. Defines how many results should be fetched when fetching random movies.
differentFrom ImdbID undefined Optional. Defines a base ImdbID that will not be included in any random results.
notReleasedIn Integer undefined Optional. Defines a release year that movies in the random results should not be from. *

* If notReleasedIn is specified, the random results will have distinct release years (i.e., the release year will be different from the specified release year and there will not be two random results having the same release year.).

API: Poster Service

Go to Repository

This service is responsible for returning urls of movie poster images for a specific IMDb id. It acts as a middleware and talks directly to https://www.omdbapi.com/. From there it fetches the poster-url and returns it.

query

/?id=<id>&size=<size>

Parameter Type Description
id string Required. IMDb ID, unique to a film
size number Optional. Size of the movie poster. Integer between [300, 1000]

Examples

Fetching a Poster by ID

http://localhost:3002/?id=tt1477834
{
  "poster": "https://m.media-amazon.com/images/M/MV5BOTk5ODg0OTU5M15BMl5BanBnXkFtZTgwMDQ3MDY3NjM@._V1_SX300.jpg"
}

Fetching a Poster by ID with Size

http://localhost:3002/?id=tt3896198&size=450
{
  "poster": "https://m.media-amazon.com/images/M/MV5BNjM0NTc0NzItM2FlYS00YzEwLWE0YmUtNTA2ZWIzODc2OTgxXkEyXkFqcGdeQXVyNTgwNzIyNzg@._V1_SX450.jpg"
}